#!/usr/bin/env python
#
# Copyright (C) 2020 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import argparse
import re
import shlex
import configparser
import collections

Build = collections.namedtuple("Build", "env extends build_flags build_src_flags")


def expand_variables(cfg, value):
    RE_VARS = re.compile(r"\$\{.*?\}")

    for var in RE_VARS.findall(value):
        section, option = var.replace("${", "").replace("}", "").split(".", 1)
        value = value.replace(var, expand_variables(cfg, cfg.get(section, option)))

    return value


def get_builds(cfg):
    RE_NEWLINE = re.compile(r"\r\n|\n")
    BASE_BUILD_FLAGS = set(
        shlex.split(expand_variables(cfg, cfg.get("env", "build_flags")))
    )

    for section in cfg.sections():
        if (not section.startswith("env:")) or (
            section.startswith("env:esp8266-") and section.endswith("-base")
        ):
            continue

        build_flags = None
        build_src_flags = None

        try:
            build_flags = cfg.get(section, "build_flags")
            build_flags = RE_NEWLINE.sub(" ", build_flags).strip()
            build_flags = " ".join(
                BASE_BUILD_FLAGS ^ set(shlex.split(expand_variables(cfg, build_flags)))
            )
        except configparser.NoOptionError:
            pass

        try:
            build_src_flags = cfg.get(section, "build_src_flags")
            build_src_flags = RE_NEWLINE.sub(" ", build_src_flags).strip()
            build_src_flags = expand_variables(cfg, build_src_flags)
        except configparser.NoOptionError:
            pass

        yield Build(
            section.replace("env:", ""),
            cfg.get(section, "extends").replace("env:", ""),
            build_flags,
            build_src_flags,
        )


def find_any(string, values):
    for value in values:
        if value in string:
            return True

    return False


def generate_lines(builds, ignore):
    minimal = []
    generic = []

    for build in builds:
        if find_any(build.env, ignore):
            continue

        flags = []
        if build.build_flags:
            flags.append(f'PLATFORMIO_BUILD_FLAGS="{build.build_flags}"')
        if build.build_src_flags:
            flags.append(f'ESPURNA_FLAGS="{build.build_src_flags}"')
        flags.append(f'ESPURNA_BUILD_NAME="{build.env}"')

        cmd = ["env"]
        cmd.extend(flags)
        cmd.extend(["pio", "run", "-e", build.extends, "-s", "-t", "build-and-copy"])

        line = " ".join(cmd)

        # push minimal variants to the front as they definetly include global build_flags
        output = generic
        if "ESPURNA_MINIMAL" in build.build_src_flags:
            output = minimal

        output.append(line)

    return minimal + generic


def every(seq, nth, total):
    index = 0
    for value in seq:
        if index == nth:
            yield value
        index = (index + 1) % total


def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--destination", help="Where to place the resulting .bin", required=True
    )
    parser.add_argument(
        "--single-source",
        help="Combine .cpp files into one to speed up compilation",
        action="store_true",
        default=True,
    )
    parser.add_argument(
        "--ignore", help="Do not build envs that contain the string(s)", action="append"
    )

    builder_nth = parser.add_argument_group(title="Synchronize parallel builds in CI")
    builder_nth.add_argument("--builder-id", type=int, required=True)
    builder_nth.add_argument("--builder-total", type=int, required=True)

    full_version = parser.add_argument_group(
        title="Fully replace the version string for the build system"
    )
    full_version.add_argument("--full-version")

    version_parts = parser.add_argument_group(
        "Replace parts of the version string that would have been detected by the build system"
    )
    version_parts.add_argument("--version")
    version_parts.add_argument("--revision")
    version_parts.add_argument("--suffix")

    return parser.parse_args()


if __name__ == "__main__":
    args = parse_args()

    Config = configparser.ConfigParser()
    with open("platformio.ini", "r") as f:
        Config.read_file(f)

    builder_total = args.builder_total
    builder_id = args.builder_id
    if builder_id >= builder_total:
        raise ValueError(f"* {builder_id=} >= {builder_total=} *")

    builds = every(get_builds(Config), builder_id, builder_total)

    print("#!/bin/bash")
    print("set -e -x")

    variables = [
        ["ESPURNA_BUILD_DESTINATION", args.destination],
        ["ESPURNA_BUILD_SINGLE_SOURCE", int(args.single_source)],
        ["ESPURNA_BUILD_FULL_VERSION", args.full_version],
        ["ESPURNA_BUILD_VERSION", args.version],
        ["ESPURNA_BUILD_REVISION", args.revision],
        ["ESPURNA_BUILD_VERSION_SUFFIX", args.suffix],
    ]

    for var, value in variables:
        if value or not value is None:
            print(f'export {var}="{value}"')

    print('trap "ls -R ${ESPURNA_BUILD_DESTINATION}" EXIT')
    print(f'echo "Selected build ID {builder_id + 1}/{builder_total}"')
    for line in generate_lines(builds, args.ignore or ()):
        print(line)
